Python爬虫中多线程、多进程、多协程的介绍和对比

您所在的位置:网站首页 python 多线程多进程协程 Python爬虫中多线程、多进程、多协程的介绍和对比

Python爬虫中多线程、多进程、多协程的介绍和对比

2023-12-30 11:55| 来源: 网络整理| 查看: 265

前面咱们也写了好几篇爬虫的文章,都是些数据量不是很多的例子。那如果遇到数据量比较大的时候怎么办提高速度和效率呢。今天的这篇会给你答案。

一、线程、进程、协程的简介 1.同步、异步、并行、并发

同步:各个任务不是独自运行的,任务之间有一定的运行顺序 异步:各个任务是独立运行的,一个任务的运行不收另一个任务的影响 并行:同一时刻发生若干事情的情况 并发:同一时间段发生若干事情。

这里可能并行和并发不是很好记忆,我来举个例子把。小明正在写作业,小红正在看电视。两件事情同时进行,这叫并行。如果小明看一眼电视,看一眼作业,这叫做并发,并不能同时做这两件事,只不过这两件事切换比较快,看起来像是同时在发生。

在单核CPU时,所有任务都是以并发的形式来运行的, 在多核CPU情况下,才会有真正的并行的形式运行任务。

2.线程 操作系统进行运算的最小单位线程是独立调度和分派的基本单位同一个进程中的所有线程可以共享该进程的资源同一进程中的所有线程均可并发执行由于Python中GIL(全局解释器锁)的存在,线程之间只能以并发执行存在。 3.进程 系统进行资源分配和调度的基本单位进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程运行中的进程可能存在以下三种情况:就绪、运行、阻塞 4.协程 一种用户态的轻量级线程。协程方便切换控制流具有很好的高扩展性和高并发行本质是单线程 二、多线程、多进程、多协程者的区别

图1 多线程的运行方式多线程的运行方式 图2 多进程的运行方式 在这里插入图片描述 图3 多协程的运行方式 在这里插入图片描述

每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。线程可以使用多核CPU,但是协程不可以。如果是 I/O 密集型,且 I/O 请求比较耗时的话,使用协程。 如果是 I/O 密集型,且 I/O 请求比较快的话,使用多线程。 如果是 计算 密集型,考虑可以使用多核 CPU,使用多进程。 三、三种爬虫方式的实验对比

本次实验是爬取豆瓣top250电影,输出电影的名字。 具体的分析我就不写了,主要是对比这三种方法的速度快慢

1.多进程 from multiprocessing import Process,Queue,Pool,Manager import requests import time from bs4 import BeautifulSoup def crawler_run(q,index): Process_id='Process--'+str(index) while not q.empty(): num = q.get(timeout=2) soup = connec_douban(num) titles = soup.findAll("div",class_="item")#.find("span",class_="title").text for title in titles: print(Process_id,q.qsize(),title.find("span",class_="title").text+"\n") def connec_douban(num1): link="https://movie.douban.com/top250?start="+str(num1)+"&filter=" headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36' } re = requests.get(link,headers=headers) soup = BeautifulSoup(re.text,"lxml") print("url-status",re.status_code,end="\n") return soup if __name__=='__main__': start = time.time() manager = Manager() p = Pool(4) q = manager.Queue(10) #q = Queue(10) for j in range(0,225,25): q.put(j) for i in range(4): p.apply_async(func=crawler_run,args=(q,i)) print("Start Process",i) p.close() p.join() end = time.time() print('Pool+Queue 的多进程爬虫的总时间为',end-start) print("END") 2.多线程 import requests from bs4 import BeautifulSoup import time import threading import queue class Mythreading(threading.Thread): def __init__(self,q,name): threading.Thread.__init__(self) self.q = q self.name=name def run(self):#这个run方法在进程创建后会自动运行这个程序 # print("starting"+self.name) # print(self.q.qsize()) # print(self.q.empty()) while not self.q.empty(): crawler_run(self.q,self.name) print("exiting...") def crawler_run(q,name): num = q.get(timeout=2) soup = connec_douban(num) #print("run-run") titles = soup.findAll("div",class_="item")#.find("span",class_="title").text for title in titles: print(name,q.qsize(),title.find("span",class_="title").text+"\n") def connec_douban(num1): link="https://movie.douban.com/top250?start="+str(num1)+"&filter=" headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36' } re = requests.get(link,headers=headers) soup = BeautifulSoup(re.text,"lxml") # print("url-status",re.status_code,end="\n") return soup if __name__=='__main__': threadset=[] start = time.time() q = queue.Queue(14) for i in range(0,326,25): q.put(i) ThreadNames=["process1","process2","process3","process4"] for i in range(0,4): p = Mythreading(q,ThreadNames[i]) p.start() threadset.append(p) for thread in threadset: thread.join() end = time.time() print("总时间:",end-start) 3.多协程 import gevent from gevent.queue import Queue,Empty from gevent import monkey monkey.patch_all()#将IO转化为异步执行的函数 import time from bs4 import BeautifulSoup import requests link_list=[] def crawler_run(index): processid = 'process-'+str(index) while not workqueue.empty(): num = workqueue.get(timeout=2) soup = connec_douban(num) titles = soup.findAll("div",class_="item")#.find("span",class_="title").text for title in titles: print(processid,workqueue.qsize(),title.find("span",class_="title").text+"\n") def connec_douban(num1): link="https://movie.douban.com/top250?start="+str(num1)+"&filter=" headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36' } re = requests.get(link,headers=headers) soup = BeautifulSoup(re.text,"lxml") # print("url-status",re.status_code,end="\n") return soup def boss(): for i in range(0,226,25): workqueue.put_nowait(i) if __name__=='__main__': start = time.time() workqueue = Queue(10) gevent.spawn(boss).join() jobs = [] for i in range(4): jobs.append(gevent.spawn(crawler_run,i)) gevent.joinall(jobs) end = time.time() print("总时间为",end-start)

三种方案我们均运行五次,取平均运行时间

方式个数时间协程41.3279s线程41.7486s进程43.1559s

由于这个实验的数据量不是很大,所以这个结果不具有代表性,但是也能够反映一些问题。

结论:针对数据量比较小,而且I/O比较频繁的,推荐使用协程。当然,你也可以将他们结合起来使用。

参考来源: [1]:进程、线程、协程的理解 [2]:进程和线程、协程的区别 [3]:百度百科-协程 [4]:百度百科-线程 [5:]百度百科-进程



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3